package com.darkdev.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.net.URL;
import java.net.URLClassLoader;

public class ClassInfo {
	private StringBuffer str = new StringBuffer();
	public ClassInfo(String name) {
		this(name,ClassInfo.class.getClass().getClassLoader());
	}
	public ClassInfo(String name, URL... urls) {
		this(name,new URLClassLoader(urls));		
	}
	public ClassInfo(String name,ClassLoader loader){
		try {
			Class<?> c = Class.forName(name, false, loader);
			// Check For ClassType
			if (c.isInterface()) str.append("('IFC',");
			else if (c.isEnum()) str.append("('ENM',");
			else str.append("('CLS',");
			// Add Name
			str.append("'").append(name).append("',");
			// Add Simple Name
			str.append("'").append(c.getSimpleName()).append("',");
			// Add Parent 
			if (c.isArray()) {
				str.append("'").append("").append("',");
			}
			else if (!c.isMemberClass()) {
				str.append("'").append(c.getPackage().getName()).append("',");
			} else {
				str.append("'").append(c.getEnclosingClass().getName()).append("',");
			}
			// Add Visibility and Modifiers
			str.append(modifiers(c.getModifiers(),c.isInterface()));
			// Add Sub-Classes
			str.append("(");
			for (Class<?> m:c.getDeclaredClasses()) {
				if (!m.isAnonymousClass() && !m.isInterface() && !m.isEnum()) 
					str.append(new ClassInfo(m.getName(),loader).toString()).append(",");
			}
			str.append("),");
			// Add Sub-Interfaces
			str.append("(");
			for (Class<?> m:c.getDeclaredClasses()) {
				if (!m.isAnonymousClass() && m.isInterface()) 
					str.append(new ClassInfo(m.getName(),loader).toString()).append(",");
			}
			str.append("),");
			// Add Sub-Enums
			str.append("(");
			for (Class<?> m:c.getDeclaredClasses()) {
				if (!m.isAnonymousClass() && m.isEnum()) 
					str.append(new ClassInfo(m.getName(),loader).toString()).append(",");
			}
			str.append("),");
			// Add Methods
			str.append("(");
			for (Method m:c.getMethods()) {
				str.append(methodInfo(m,c.isInterface()));
			}
			str.append("),");
			// Add Fields
			str.append("(");
			for (Field m:c.getFields()) {
				str.append(fieldInfo(m,c.isInterface()));
			}
			str.append("),(");
			// Add Generic Variables
			for (TypeVariable t:c.getTypeParameters()) {
				str.append("'").append(t.getName()).append("',");
			}
			str.append(")");
			if (str.charAt(2)!='I'){
				// Add Constructors
				str.append(",(");
				for (Constructor<?> m:c.getConstructors()) {
					str.append(constructorInfo(m));
				}
				str.append(")");
			}
			str.append(",(");
			// Add Implements For Class/Enum and Extends For Interface
			for (Class<?> i:c.getInterfaces()) {
				str.append("'").append(getFileName(i)).append("',");
			}
			str.append(")");
			if (str.charAt(2)=='C') {
				// Add Extends For Class
				str.append(",('").append(getFileName(c.getSuperclass())).append("')");
			}
			// Close
			str.append(")");
		} catch (ClassNotFoundException e) {
			str = str.append("None");
		}
	}
	private String constructorInfo(Constructor<?> m){
		StringBuffer sb = new StringBuffer("('CNR','");
		// Add Name
		sb.append(m.getName()).append("','");
		// Add Declaring Class Name
		sb.append(getFileName(m.getDeclaringClass())).append("',");
		// Add Visibility / Modifiers
		sb.append(modifiers(m.getModifiers(),false));
		// Add Args
		sb.append("(");
		for (Type t:m.getGenericParameterTypes()) {
			sb.append("'").append(getFileName(t)).append("',");
		}
		if (m.isVarArgs()) sb.replace(sb.length()-4,sb.length(),"...',");
		// Add Throws
		sb.append("),(");
		for (Type t:m.getGenericExceptionTypes()) {
			sb.append("'").append(getFileName(t)).append("',");
		}
		return sb.append(")),").toString();
	}
	private String fieldInfo(Field m, boolean ifc){
		StringBuffer sb = new StringBuffer("('FLD','");
		// Add Name
		sb.append(m.getName()).append("','");
		// Add Declaring Class Name
		sb.append(getFileName(m.getDeclaringClass())).append("',");
		// Add Visibility / Modifiers
		sb.append(modifiers(m.getModifiers(),ifc));
		// Add Type
		sb.append("'").append(getFileName(m.getGenericType())).append("'");
		return sb.append("),").toString();
	}
	private String methodInfo(Method m, boolean ifc){
		StringBuffer sb = new StringBuffer("('MTH','");
		// Add Name
		sb.append(m.getName()).append("','");
		// Add Declaring Class Name
		sb.append(getFileName(m.getDeclaringClass())).append("',");
		// Add Visibility / Modifiers
		sb.append(modifiers(m.getModifiers(),ifc));
		// Add Args
		sb.append("(");
		for (Type t:m.getGenericParameterTypes()) {
			sb.append("'").append(getFileName(t)).append("',");
		}
		if (m.isVarArgs()) sb.replace(sb.length()-4,sb.length(),"...',");
		// Add Throws
		sb.append("),(");
		for (Type t:m.getGenericExceptionTypes()) {
			sb.append("'").append(getFileName(t)).append("',");
		}
		// Add Return Type
		sb.append("),'").append(getFileName(m.getGenericReturnType())).append("'),");
		return sb.toString();
	}
	private String modifiers(int mod, boolean ifc) {
		StringBuffer sb = new StringBuffer("'");
		if (Modifier.isPublic(mod)) sb.append("public");
		else if (Modifier.isProtected(mod)) sb.append("protected");
		else if (Modifier.isPrivate(mod)) sb.append("private");
		sb.append("',(");
		// Obviously Interfaces Are Abstract So No Need To Add It
		if (!ifc && Modifier.isAbstract(mod)) sb.append("'abstract',");
		if (Modifier.isFinal(mod)) sb.append("'final',");
		if (Modifier.isNative(mod)) sb.append("'native',");
		if (Modifier.isStatic(mod)) sb.append("'static',");
		if (Modifier.isStrict(mod)) sb.append("'strictfp',");
		if (Modifier.isSynchronized(mod)) sb.append("'synchronized',");
		if (Modifier.isTransient(mod)) sb.append("'transient',");
		if (Modifier.isVolatile(mod)) sb.append("'volatile',");
		return sb.append("),").toString();
	}
	public String toString() {
		return str.toString();
	}
	public String getFileName(Object c) {
		if (c==null) return "";
		String x = c.toString();
		if (x.startsWith("class ")) x=x.substring(6);
		else if (x.startsWith("interface ")) x=x.substring(10);
		else if (x.startsWith("enum ")) x=x.substring(5);
		if (x.startsWith("[")) {
			int a = x.length()-x.replace("[","").length();
			String n = x.substring(a).replace(";","");
			if (n.equals("Z")) return addBrackets("boolean",a);
			if (n.equals("B")) return addBrackets("byte",a);
			if (n.equals("C")) return addBrackets("char",a);
			if (n.equals("D")) return addBrackets("double",a);
			if (n.equals("F")) return addBrackets("float",a);
			if (n.equals("I")) return addBrackets("int",a);
			if (n.equals("J")) return addBrackets("long",a);
			if (n.equals("S")) return addBrackets("short",a);
			if (n.startsWith("L")) return addBrackets(n.substring(1).replaceAll("(^|\\<|,)(.+)\\.\\2\\$","$1$2\\$"),a);
		}
		return x.replaceAll("(^|\\<|,)(.+)\\.\\2\\$","$1$2\\$");
	}
	private String addBrackets(String str, int count) {
		StringBuffer sb = new StringBuffer(str);
		for (int i=0;i<count;i++) sb.append("[]");
		return sb.toString();
	}
}
